library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.processor_types.all;

architecture behaviour of processor is
begin
    process
        constant allzero: std_logic_vector(31 downto 0) := (others => '0');
        variable pc, newpc : natural;
        variable current_instr : bit32;
            alias op    : bit6 is current_instr(31 downto 26);
            alias func  : bit6 is current_instr(5 downto 0);
            alias rs    : bit5 is current_instr(25 downto 21);
            alias rt    : bit5 is current_instr(20 downto 16);
            alias rd    : bit5 is current_instr(15 downto 11);
            alias rh    : bit5 is current_instr(10 downto 6);
            
            alias address : bit26 is current_instr(25 downto 0);
            alias imm     : bit16 is current_instr(15 downto 0);
        
        variable rs_int, rt_int, rd_int, rh_int : integer;
        variable address_int : integer;
        variable imm_int : integer;
        variable val_int : integer;
        variable val_long : signed(63 downto 0);
        
        variable registers : registers32;
        variable reg_hi, reg_lo : bit32;
        
        procedure memory_read(addr : in natural; result : out bit32) is
        begin
            a_bus <= std_logic_vector(to_unsigned(addr, 32));
            wait until clock = '1';
            if reset = '1' then
                return;
            end if;
            
            loop
                if reset = '1' then
                    return;
                end if;
                exit when ready = '0';
                wait until clock = '1';
            end loop;
            
            read <= '1' after 5 ns;
            wait until clock = '1';
            if reset = '1' then
                return;
            end if;
            
            loop
                wait until clock = '1';
                if reset = '1' then
                    return;
                end if;
                if ready = '1' then
                    result := d_busin;
                    exit;
                end if;
            end loop;
            
            wait until clock = '1';
            if reset = '1' then
                return;
            end if;
            
            read <= '0' after 5 ns;
            a_bus <= dontcare after 5 ns;
        end memory_read;
        
        procedure memory_write(addr : in natural; data : in bit32) is
        begin
            a_bus <= std_logic_vector(to_unsigned(addr, 32));
            wait until clock = '1';
            if reset = '1' then
                return;
            end if;
            
            loop
                if reset = '1' then
                    return;
                end if;
                exit when ready = '0';
                wait until clock = '1';
            end loop;
            
            d_busout <= data;
            wait until clock = '1';
            if reset = '1' then
                return;
            end if;
            write <= '1';
            
            loop
                wait until clock = '1';
                if reset = '1' then
                    return;
                end if;
                exit when ready = '1';
            end loop;
            
            wait until clock = '1';
            if reset = '1' then
                return;
            end if;
            
            write <= '0';
            a_bus <= dontcare;
            d_busout <= dontcare;
        end memory_write;
    begin

        if reset='1' then
            read <= '0';
            write <= '0';
            pc := 0;
            done <= '0';
            reg_hi := (others => '0');
            reg_lo := (others => '0');
            res <= allzero;
            for i in 0 to 31 loop
                registers(i) := (others => '0');
            end loop;
            loop
                wait until clock='1';
                exit when reset='0';
            end loop;
        else        
            --done <= '1';
            wait until clock='1';
        
			done <= '0';
			memory_read(pc, current_instr);
			if reset /= '1' then
				newpc := pc + 4;
				case op is
					when rtype  =>  rs_int := to_integer(unsigned(rs));
									rt_int := to_integer(unsigned(rt));
									rd_int := to_integer(unsigned(rd));
									rh_int := to_integer(unsigned(rh));
									
									case func is
										when add    => registers(rd_int) := std_logic_vector(signed(registers(rs_int)) + signed(registers(rt_int)));
													   res <= registers(rd_int);
										when mfhi   => registers(rd_int) := reg_hi;
													   res <= registers(rd_int);
										when mflo   => registers(rd_int) := reg_lo;
													   res <= registers(rd_int);
										when mult   =>  val_long := signed(registers(rs_int)) * signed(registers(rt_int));
														reg_hi := std_logic_vector(val_long(63 downto 32));
														reg_lo := std_logic_vector(val_long(31 downto 0));
														res <= (others=>'-');
														--res <= (others=>'0');
										when f_sll  => registers(rd_int) := std_logic_vector(unsigned(registers(rt_int)) sll rh_int);
													   res <= registers(rd_int);
										when sltu   =>  if unsigned(registers(rs_int)) < unsigned(registers(rt_int)) then
															registers(rd_int) := (0 => '1', others => '0');
															res <= registers(rd_int);
														else
															registers(rd_int) := (others => '0');
															res <= registers(rd_int);
														end if;
										when f_nor  => registers(rd_int) := std_logic_vector(unsigned(registers(rs_int)) nor unsigned(registers(rt_int)));
													   res <= registers(rd_int);
										when others => assert false report "illegal r-type function detected" severity warning;
									end case;
					
					when blez   =>  rs_int := to_integer(unsigned(rs));
									val_int := to_integer(signed(registers(rs_int)));
									imm_int := to_integer(signed(imm) sll 2);
									if val_int <= 0 then
										newpc := newpc + imm_int;
									end if;
									res <= std_logic_vector(to_unsigned(newpc,32));
					
					when j      =>  newpc := to_integer((to_unsigned(newpc, 32) and X"f0000000") or ("000000" & (unsigned(address) sll 2)));
									res <= std_logic_vector(to_unsigned(newpc,32));
					
					when lw     =>  rs_int := to_integer(unsigned(rs));
									rt_int := to_integer(unsigned(rt));
									imm_int := to_integer(signed(imm));
									memory_read(to_integer(unsigned(registers(rs_int))) + imm_int, registers(rt_int));
									res <= (others => '-');
					
					when sw     =>  rs_int := to_integer(unsigned(rs));
									rt_int := to_integer(unsigned(rt));
									imm_int := to_integer(signed(imm));
									memory_write(to_integer(unsigned(registers(rs_int))) + imm_int, registers(rt_int));
									res <= registers(rt_int);
									
					when others => assert false report "illegal instruction detected" severity warning;
				end case;
				pc := newpc;
				done <= '1';
			end if;
        end if;
    end process;
end behaviour;
